import sys, socket, threading, json, os, time

from PyQt5.QtGui import QIntValidator

from mainwindow import *

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtCore import pyqtSlot, pyqtSignal


class MainWindow(QMainWindow):
    accept_signal = pyqtSignal(str)
    updata_ui_signal = pyqtSignal(int, str, str, int)
    close_listen_signal = pyqtSignal()
    start_listen_signal = pyqtSignal()
    start_button_signal = pyqtSignal(bool)

    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        # self.host_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # self.recv_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.ui.port.setValidator(QIntValidator(0, 65535))
        self.ui.recv_port.setValidator(QIntValidator(0, 65535))
        self.reply = QMessageBox.No
        self.lock = threading.Lock()
        self.file_names = list()
        self.folder = str()
        self.accept_signal.connect(self.accept_or_not)
        self.updata_ui_signal.connect(self.updata_ui)
        self.close_listen_signal.connect(self.close_listen)
        self.start_listen_signal.connect(self.on_listen_clicked)
        self.start_button_signal.connect(self.start_button_switch)
        self.host_s = None
        self.recv_s = None
        self.client_list = {}
        self.discovery = True
        self.listen_state = False
        self.listen_thread = None
        self.send_thread = None
        self.discovery_thread = None
        self.thread_Daemon = True
        self.hostname = socket.gethostname()
        # 把获取到的IP放进去
        self.ui.host_ip.addItem("all")
        _ = socket.gethostbyname_ex(socket.gethostname())[2]
        self.ui.host_ip.addItems(_)
        _ = os.path.join(os.getcwd(), "saved")
        self.config = {
            'savepath': _
        }
        self.ui.path.setText(self.config['savepath'])
        # 开启网络发现 设置网络发现线程变量
        if self.discovery:
            self.discovery_thread = threading.Thread(name='discovery', target=self.discovery_server)
            self.discovery_thread.setDaemon(True)
            self.discovery_thread.start()
            print("Discovery......")
            self.ui.message.appendPlainText("Discovery......")

        print('init done! wait!')
        self.ui.message.appendPlainText('init done!')

    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
        # self.thread_Daemon = False
        for th in threading.enumerate():
            print(th)
        # if self.send_thread:
        #     self.send_thread._stop()
        # if self.listen_thread:
        #     self.listen_thread._stop()
        # if self.discovery_thread:
        #     self.discovery_thread._stop()
        a0.accept()

    @pyqtSlot()
    def on_path_button_clicked(self):
        path = QFileDialog.getExistingDirectory(self, "选择文件夹")
        self.config['savepath'] = path
        self.ui.path.setText(path)
    @pyqtSlot()
    def on_close_clicked(self):
        try:
            self.host_s.shutdown(socket.SHUT_RDWR)
            self.host_s.close()
            self.host_s = None
            self.ui.ip.setEnabled(True)
            self.ui.port.setEnabled(True)
            self.ui.close.setDisabled(True)
            self.ui.connect.setDisabled(False)
            if not self.client_list:
                self.ui.start.setDisabled(True)
            self.ui.info.setText("已关闭")
        except Exception as e:
            print(e)
            self.ui.ip.setEnabled(False)
            self.ui.port.setEnabled(False)
            self.ui.close.setDisabled(True)
            self.ui.connect.setDisabled(False)
            self.ui.start.setDisabled(True)
            self.ui.info.setText("关闭失败")
            return

    @pyqtSlot()
    def on_connect_clicked(self):
        try:
            ip = self.ui.ip.text()
            port = int(self.ui.port.text())
            self.connect_ui_init(ip, port)
        except Exception as e:
            self.ui.info.setText("连接失败")

    @pyqtSlot()
    def on_listen_clicked(self):
        if not self.listen_state:
            # 设置状态
            self.listen_state = True
            self.ui.listen.setText('断开')
            # self.recv_s.close()
            # self.ui.listen.setDisabled(True)
            self.ui.host_ip.setDisabled(True)
            self.ui.recv_port.setDisabled(True)
            print(self.ui.host_ip.currentText())
            self.recv_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            ip = self.ui.host_ip.currentText()
            port = int(self.ui.recv_port.text())
            if self.ui.host_ip.currentText() == 'all':
                ip = ""
            self.recv_s.bind((ip, port))
            self.recv_s.listen(1)
            self.listent()
        else:
            # self.ui.listen.setDisabled(False)
            self.ui.listen.setText('监听')
            self.listen_state = False
            self.ui.host_ip.setDisabled(False)
            self.ui.recv_port.setDisabled(False)
            self.recv_s.close()
            self.recv_s = None

    @pyqtSlot()
    def on_start_clicked(self):
        print("开始发送")
        self.ui.message.appendPlainText('开始发送~~~~~~')
        try:
            if not self.host_s:
                # 判断是否已经有一个连接
                print("conncet to selected client!")
                self.ui.message.appendPlainText("conncet to selected client!")
                addr = self.client_list[self.ui.clients_hostname.currentText()]
                self.connect_ui_init(addr['ip'],addr['port'])
            self.start_button_signal.emit(False)
            self.send_thread = threading.Thread(name='sending', target=self.send_file)
            self.send_thread.setDaemon(True)
            self.send_thread.start()
        except Exception as e:
            print("发生错误", e)
            self.on_close_clicked()
            # 恢复发送按钮
            self.start_button_signal.emit(True)
            self.ui.info.setText("发生错误")
            self.host_s.close()
            self.host_s = None
        except ConnectionResetError as e:
            print("Fasdfasdfasfaf")

    @pyqtSlot()
    def on_choosedir_clicked(self):
        self.file_names = list()
        self.folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
        for root, dirs, files in os.walk(self.folder):
            for file in files:
                self.file_names.append(os.path.relpath(os.path.join(root, file), self.folder))
        print(self.folder, self.file_names)
        self.ui.file_box.setPlainText(str(self.folder))
        self.flag = 'dir'
        # 进行客户端搜索
        self.discovery_find()

    @pyqtSlot()
    def on_choosefile_clicked(self):
        self.file_names = QFileDialog.getOpenFileNames(self, "选择文件")[0]
        self.folder = ""
        self.ui.file_box.setPlainText("\n".join(self.file_names))
        self.flag = 'files'
        print(self.folder, self.file_names)
        # 进行客户端搜索
        self.discovery_find()

    # function
    def connect_ui_init(self, ip, port):
        try:
            self.ui.ip.setText(ip)
            self.ui.port.setText(str(port))
            self.ui.ip.setDisabled(True)
            self.ui.port.setDisabled(True)
            self.ui.info.setText("正在连接...")
            self.connect(ip, port)
            self.ui.connect.setDisabled(True)
            self.ui.close.setDisabled(False)
            self.ui.start.setEnabled(True)
            self.ui.info.setText("已连接")
        except Exception as e:
            print(e)
            self.ui.connect.setDisabled(False)
            self.ui.close.setDisabled(True)
            self.ui.start.setDisabled(True)
            self.ui.ip.setEnabled(True)
            self.ui.port.setEnabled(True)
            self.ui.info.setText("连接失败")
            raise Exception(e)

    def start_button_switch(self, switch):
        # 切换开始按钮的状态
        self.ui.start.setEnabled(switch)

    def close_listen(self):
        # 关闭监听的套接字，并且丢弃
        try:
            self.recv_s.close()
        except:
            pass
        finally:
            self.recv_s = None
            self.ui.listen.setEnabled(True)
            self.ui.host_ip.setEnabled(True)
            self.ui.recv_port.setEnabled(True)

    def accept_or_not(self, msg):
        reply = QMessageBox.question(self, "提示", msg,
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if reply == QMessageBox.Yes:
            self.reply = QMessageBox.Yes
        else:
            self.reply = QMessageBox.No
        self.lock.release()

    def updata_ui(self, process: int, speed: str, info: str, file_process: int):
        self.ui.progress.setValue(process)
        self.ui.speed.setText(speed)
        self.ui.info.setText(info)
        self.ui.file_progress.setValue(file_process)

    def send_file(self):
        try:
            total_num = len(self.file_names)
            self.host_s.send(('upload' + str(total_num)).encode('utf-8'))
            msg = self.host_s.recv(1024).decode('utf-8')
            info = "共{}个文件".format(total_num)
            print(info)
            self.ui.message.appendPlainText(info)
            # 计算总大小
            total_data = 0
            for i in self.file_names:
                total_data += os.path.getsize(os.path.join(self.folder, i))

            # 如果接收到accept就发送。否则退出
            if msg == 'accept':
                self.host_s.send(str(total_data).encode('utf-8'))
                current_num = 0
                current_data = 0
                for name in self.file_names:
                    info = "当前{}个，共{}个".format(current_num + 1, total_num)
                    print(info)
                    # 发送部分
                    # 文件在本机的地址
                    if self.flag == 'dir':
                        file = os.path.join(self.folder, name)
                        filename = name
                    else:
                        file = name
                        filename = os.path.basename(name)
                    # 发送文件信息
                    folder = os.path.basename(self.folder)
                    size = os.path.getsize(file)
                    header = dict(filename=filename, folder=folder, size=size)
                    header = json.dumps(header)
                    self.host_s.send(header.encode('utf-8'))
                    print("header sended")
                    # 发送
                    msg = self.host_s.recv(1024)
                    print(msg.decode('utf-8'), "开始正常传输")
                    self.ui.message.appendPlainText(msg.decode('utf-8') + "开始正常传输")
                    start_time = time.time()
                    with open(file, 'rb') as f:
                        count = 0
                        speed_count = 0
                        while count < size:
                            data = f.read(4096)
                            data_size = len(data)
                            count += data_size
                            speed_count += data_size
                            self.host_s.sendall(data)

                            # 计算速度和进度
                            current_data += data_size
                            delta_time = time.time() - start_time
                            if delta_time > 1:
                                # 设置ui更新,刷新一次
                                start_time = time.time()
                                try:
                                    # (speedcount/1048576) 算出这段时间内接收的mb量
                                    speed = "{}M/s".format(round((speed_count / 1048576) / delta_time, 2))
                                except:
                                    speed = "##M/s"
                                level = int((current_data / total_data) * 100)
                                file_level = int((count / size) * 100)
                                self.updata_ui_signal.emit(level, speed, info, file_level)
                                speed_count = 0

                    # 单个发送完成
                    self.updata_ui_signal.emit(int((current_data / total_data) * 100), "##M/s", info, 100)
                    msg = self.host_s.recv(1024).decode('utf-8')
                    # if msg == 'next':
                    #     print("continue")
                    #     continue
                    # else:
                    #     break
                    current_num += 1
                    # 发送部分

                # 全部文件发送完成的标记
                self.updata_ui_signal.emit(100, "##M/s", '发送完成', 100)
                print("send_complete")
                self.ui.message.appendPlainText('send_complete')
                self.host_s.send('complete'.encode('utf-8'))
                # self.host_s.close()
                # self.ui.speed.setText("断开连接")
                self.start_button_signal.emit(True)
                return
            else:
                print(msg)
                self.start_button_signal.emit(True)
                return
        except Exception as e:
            print("发生错误", e)
            self.start_button_signal.emit(True)
            self.on_close_clicked()
            self.ui.info.setText("发生错误")

    def listent(self):
        self.listen_thread = threading.Thread(name='listening', target=self.wait_for_conncet)
        self.listen_thread.setDaemon(True)
        self.listen_thread.start()
        print("开始监听")
        self.ui.message.appendPlainText('开始监听~~~~~~')

    def wait_for_conncet(self):
        while 1:
            try:
                print("等待连接")
                self.ui.message.appendPlainText("等待连接")
                client_s, client_addr = self.recv_s.accept()

                # 请求双向连接
                if (not self.host_s) and self.ui.dual.isChecked():
                    # 检测本机还没有连接其他机器，发送双向连接请求
                    print("检测本机还没有连接其他机器，发送双向连接请求")
                    self.ui.message.appendPlainText("检测本机还没有连接其他机器，发送双向连接请求")
                    client_s.sendall('dual'.encode('utf-8'))
                    msg = client_s.recv(1024).decode('utf-8')
                    if msg[0:6] == 'accept':
                        ip = client_addr[0]
                        port = int(msg[6:])
                        print(ip, port, "建立连接中")
                        self.ui.message.appendPlainText(ip + port + "建立连接中")
                        # 开启dual标记，跳过检查
                        try:
                            self.connect_ui_init(ip, port)
                            client_s.sendall('success'.encode('utf-8'))
                            print("连接成功-----")
                            self.ui.message.appendPlainText("连接成功-----")
                        except:
                            # 如果失败了，就发送失败信号，让对方关闭监听，保持现在的连接状态
                            client_s.sendall('failure'.encode('utf-8'))
                        pass
                    elif msg == 'reject':
                        print("对方拒绝")
                        self.ui.message.appendPlainText("对方拒绝")
                        pass
                else:
                    client_s.sendall('single'.encode('utf-8'))
                    if self.host_s:
                        print("我方已经有连接了")
                        self.ui.message.appendPlainText('我方已经有连接了')
                    if self.ui.single.isChecked():
                        print("我方开启了单向")
                        self.ui.message.appendPlainText('我方开启了单向')
                    pass

                # 开始等待接受文件
                self.recv_mes(client_addr, client_s)
                print("我方断开连接了")
                self.ui.message.appendPlainText('我方断开连接了')

            except Exception as e:
                # 发生错误，重置监听
                self.recv_s = None
                self.ui.host_ip.setEnabled(True)
                self.ui.recv_port.setEnabled(True)
                self.ui.listen.setEnabled(True)
                self.ui.info.setText('发送方断开了连接 ' + str(e))
                print(e)
                return

    def connect(self, ip, port, des_ip=None, des_port=None):
        try:
            self.host_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # self.host_s.settimeout(2)
            print('connect to ', ip, port)
            self.ui.message.appendPlainText('{}{}{}'.format('connect to', ip, port))
            self.host_s.connect((ip, port))
            # 检测回复信息，检测是否开启双向连接
            msg = self.host_s.recv(1024).decode('utf-8')
            if msg == 'dual':
                # 如果本机还没有开启监听
                if not self.recv_s and self.ui.dual.isChecked():
                    # 开启监听
                    self.ui.host_ip.setCurrentIndex(self.ui.host_ip.count() - 1)
                    self.start_listen_signal.emit()
                    # 接受双向连接，发送接受标记
                    msg = ('accept' + self.ui.recv_port.text()).encode('utf-8')
                    print('对方正在尝试连接')
                    self.ui.message.appendPlainText('对方正在尝试连接')
                    self.host_s.sendall(msg)
                    # 检查对方连接是否成功
                    msg = self.host_s.recv(1024).decode('utf-8')
                    # msg=success or msg=failure
                    if msg == 'failure':
                        print('对方连接失败')
                        self.ui.message.appendPlainText('对方连接失败')
                        self.close_listen_signal.emit()
                        return
                    else:
                        print('对方连接成功')
                        self.ui.message.appendPlainText('对方连接成功')
                        return
                else:
                    self.host_s.sendall('reject'.encode('utf-8'))
                    # 分别产生不同提示
                    if self.ui.single.isChecked():
                        print(msg, "拒绝对方的双向连接请求，因为我方设置了单向")
                        self.ui.message.appendPlainText("拒绝对方的双向连接请求，因为我方设置了单向")
                    if self.recv_s:
                        print(msg, "拒绝对方的双向连接请求，因为我方已经开启了监听")
                        self.ui.message.appendPlainText("拒绝对方的双向连接请求，因为我方已经开启了监听")
                    return
            elif msg == 'single':
                # 不需要连接对方
                print(msg, "对方提出单向连接")
                self.ui.message.appendPlainText("对方提出单向连接")
                return

        except Exception as e:
            self.lock.release()
            print(e)
            # 发生错误，重置连接
            self.host_s = None
            self.ui.ip.setEnabled(True)
            self.ui.port.setEnabled(True)
            self.ui.start.setEnabled(True)
            self.ui.close.setEnabled(False)
            self.ui.info.setText("连接失败")
            raise Exception

    def recv_mes(self, client_addr, client_s):
        print("进入recv_mes", client_addr)
        while True:
            print("等待接收提示")
            self.ui.message.appendPlainText(("等待接收提示"))
            msg = client_s.recv(1024).decode('utf-8')
            if not msg:
                # 检测到对方断开连接
                print("对方断开连接")
                self.ui.message.appendPlainText("对方断开连接")
                client_s.shutdown(socket.SHUT_RDWR)
                client_s.close()
                return
            msg_index = msg.find("upload")
            total_num = int(msg[msg_index + 6:])
            if msg[msg_index:msg_index + 6] == 'upload':
                # 创建一个消息盒子（提示框）
                print("是否接收来自{}的{}个文件？".format(client_addr, total_num))
                self.ui.message.appendPlainText("是否接收来自{}的{}个文件？".format(client_addr, total_num))
                self.lock.acquire()
                self.accept_signal.emit("是否接收来自{}的{}个文件？".format(client_addr, total_num))
                # 加锁
                self.lock.acquire()
                if self.reply == QMessageBox.Yes:
                    # 发送开始接收文件信息
                    client_s.send('accept'.encode('utf-8'))
                    total_data = int(client_s.recv(4096).decode('utf-8'))
                    # 开启接收文件的循环
                    self.recv_mes_circal(client_s, total_num, total_data)
                else:
                    print("拒绝接收")
                    self.ui.message.appendPlainText("拒绝接收")
                    client_s.send('reject'.encode('utf-8'))
                # 解锁
                self.lock.release()
                print("解锁")
                self.ui.message.appendPlainText("解锁")
            elif not msg:
                # 检测到对方断开连接
                print("对方断开连接")
                self.ui.message.appendPlainText("对方断开连接")
                client_s.shutdown(socket.SHUT_RDWR)
                client_s.close()
                return

    def discovery_find(self):
        # 主动发出广播寻找landing客户端
        # 重置列表，重新获取客户端
        self.client_list = {}
        self.ui.clients_hostname.clear()
        self.start_button_switch(False)
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        addrs = socket.gethostbyname_ex(socket.gethostname())[2]
        # 广播三次寻找clients
        for i in range(3):
            for addr in addrs:
                ip = addr[:addr.rindex('.')] + '.255'
                s.sendto('iflanding'.encode(),(ip,12345))
                print('send to ' + ip)
                self.ui.message.appendPlainText('send to {}'.format(ip))
            # print('sleep......for 1s')
    def discovery_server(self):
        # 开启网络发现的情况下，一直监听有无其他客户端发送的广播，如果有则回报自己的文件传输端口
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.bind(('',12345))
        print("123")
        while 1:
            data, addr = s.recvfrom(1024)
            data = data.decode()
            if data == 'iflanding':
                if self.listen_state:
                    # 判断是否在监听状态
                    print('found a landing client! from ', addr)
                    self.ui.message.appendPlainText('found a landing client! from {}'.format(addr))
                    self.discovery_handout(addr[0],addr[1])
                else:
                    print('got an request, but we don want to accept, ignore!')
                    self.ui.message.appendPlainText('got an request, but we don want to accept, ignore!')
            elif data.startswith('landingclient'):
                data = data.strip('landingclient')
                client_hostname = data.split('|||')[0]
                client_port = data.split('|||')[-1]
                if client_hostname not in self.client_list and client_hostname != self.hostname:
                    # 加入client list 嵌套列表
                    self.client_list[client_hostname] = {'ip':addr[0], 'port':int(client_port)}
                    # 添加到客户端列表
                    self.ui.clients_hostname.addItem(client_hostname)
                    # 有client则开启发送按钮
                    self.start_button_signal.emit(True)
                    print('now i have client ',self.client_list)
                    self.ui.message.appendPlainText('now i have client {}'.format(self.client_list))
                else:
                    print('got an addr already have, drop!')
                    self.ui.message.appendPlainText('got an addr already have, drop!')
            else:
                print('data not from landing client, drop!')
                self.ui.message.appendPlainText('data not from landing client, drop!')
    def discovery_handout(self, desaddr,desport):
        # 功能函数，回报自己的文件传输端口
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        addr = socket.gethostbyname(socket.gethostname())
        s.sendto(('landingclient' + socket.gethostname() + '|||' + self.ui.recv_port.text()).encode(),(desaddr,12345))
        print('send my addr to ',desaddr)
        self.ui.message.appendPlainText('send my addr to {}'.format(desaddr))
    def recv_mes_circal(self, client_s, total_num, total_data):
        # 文件计数器
        current_files_count = 0
        current_data = 0
        while True:
            try:
                # 检测文件头信息
                msg = client_s.recv(4096).decode('utf-8')
                if msg == 'complete':
                    # 检测到完成
                    print("recv_complete", "===================")
                    self.ui.message.appendPlainText("recv_complete")
                    self.updata_ui_signal.emit(100, "##M/s", "完成", 100)
                    return
                # 检测到json头信息，解析
                header = json.loads(msg)
                filename, folder, size = header['filename'], header['folder'], header['size']
                print(filename, folder, size)
                self.ui.message.appendPlainText('{}{}{}'.format(filename,folder,size))
                client_s.send("nothing".encode('utf-8'))
                # 计数器加1
                current_files_count += 1

                # 设置接收文件路径
                path = os.path.join(self.config['savepath'], folder, filename)
                # 目录不存在就创建
                if not os.path.exists(os.path.dirname(path)):
                    os.makedirs(os.path.dirname(path))
                print(path)
                # 接收文件
                recvcount = 0
                with open(path, 'wb') as f:
                    # 接收文件
                    time_start = time.time()
                    # 用于测算速度的
                    speedcount = 0
                    while recvcount < size:
                        # 接收文件
                        data = client_s.recv(4096)
                        data_size = len(data)
                        if data:
                            f.write(data)
                        else:
                            raise Exception('发送方断开连接')
                        recvcount += data_size
                        # 接收文件
                        speedcount += data_size
                        current_data += data_size
                        time_delta = time.time() - time_start
                        if time_delta > 1:
                            # 设置ui更新,刷新一次
                            time_start = time.time()
                            try:
                                # (speedcount/1048576) 算出这段时间内接收的mb量
                                speed = "{}M/s".format(round((speedcount / 1048576) / time_delta, 2))
                            except:
                                speed = "##M/s"
                            time_start = time.time()
                            file_level = int((recvcount / size) * 100)
                            info = '\r当前{}/{}----{}-------{}-{}%'.format(current_files_count, total_num, path, speed,
                                                                         file_level)
                            print(info, end="")
                            speedcount = 0
                            self.updata_ui_signal.emit(int((current_data / total_data) * 100), speed, info, file_level)

                    # 发送信息，接收下一个文件
                    client_s.send('next'.encode('utf-8'))
                print("接收完成")
                self.ui.message.appendPlainText("接收完成")
                self.updata_ui_signal.emit(int((current_data / total_data) * 100), "##M/s",
                                           '当前{}/{}----{}--------%{}'.format(current_files_count, total_num, path, 100),
                                           100)

            except Exception as e:
                self.lock.release()
                print(e, "出现问题，关闭连接")
                client_s.shutdown(socket.SHUT_RDWR)
                client_s.close()
                raise Exception


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mywin = MainWindow()
    mywin.show()
    sys.exit(app.exec_())
